# -*- coding: utf-8 -*-
# __author__ = 'benxing'

import sys
import logging
import copy
import json
import time
import datetime
import traceback

import tornado.web
import tornado.httpclient
from tornado.curl_httpclient import CurlAsyncHTTPClient
import tornado.ioloop

logger = logging.getLogger('info')
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler('/home/log/phantomjs.log')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
logger.addHandler(fh)

proxy_port = str(int(sys.argv[1])-10)

def text(obj, encoding='utf-8'):
    if isinstance(obj, unicode):
        return obj.encode(encoding)
    return obj


def unicode_obj(obj, encoding='utf-8'):
    if isinstance(obj, str):
        return obj.decode(encoding)
    return obj


class Fetcher(object):
    default_options = {
        'method': 'GET',
        'headers': {},
        'allow_redirects': True,
        'use_gzip': True,
        'timeout': 120,
        'data':''
    }

    def __init__(self, phantomjs_proxy='http://localhost:'+proxy_port, user_agent='', pool_size=100, async=False):
        self.phantomjs_proxy = phantomjs_proxy
        self.user_agent = user_agent
        self.async = async
        if self.async:
            self.http_client = CurlAsyncHTTPClient(max_clients=pool_size, io_loop=tornado.ioloop.IOLoop())
        else:
            self.http_client = tornado.httpclient.HTTPClient(max_clients=pool_size)

    @staticmethod
    def parse_option(default_options, url, user_agent, **kwargs):
        fetch = copy.deepcopy(default_options)
        fetch['url'] = url
        fetch['headers']['User-Agent'] = user_agent
        js_script = kwargs.get('js_script')
        fetch['js_run_at'] = kwargs.get('js_run_at', 'document-end')
        if js_script:
            fetch['js_script'] = js_script
        fetch['load_images'] = kwargs.get('load_images', False)
        fetch['load_images'] = True
        fetch['label_name'] = kwargs.get('label_name', '')
        fetch['label_type'] = kwargs.get('label_type', '')
        return fetch

    def phantomjs_fetch(self, url, **kwargs):
        start_time = time.time()
        fetch = self.parse_option(self.default_options, url, user_agent=self.user_agent, **kwargs)
        request_conf = {
            'follow_redirects': False
        }
        if 'timeout' in fetch:
            request_conf['connect_timeout'] = fetch['timeout']
            request_conf['request_timeout'] = fetch['timeout'] + 1

        def handle_response(response):
            if not response.body:
                return handle_error(Exception('no response from phantomjs'))
            try:
                result = json.loads(text(response.body))
                if response.error:
                    result['error'] = text(response.error)
            except Exception as e:
                return handle_error(e)

            if result.get('status_code', 200):
                logger.log(logging.INFO,(__file__ + '|' + str(sys._getframe().f_lineno) + '|' + ('[%d] %s %.2fs' % (result['status_code'], url, result['time']))))
            else:
                logger.log(logging.ERROR,(__file__ + '|' + str(sys._getframe().f_lineno) + '|' + ('[%d] %s, %r %.2fs' % (result['status_code'],
                              url, result['content'], result['time']))))
            return result

        def handle_error(error):
            result = {
                'status_code': getattr(error, 'code', 500),
                'error': unicode_obj(error),
                'content': '',
                'time': time.time() - start_time,
                'orig_url': url,
                'url': url,
            }
            logger.log(logging.ERROR,(__file__ + '|' + str(sys._getframe().f_lineno) + '|' + ('[%d] %s, %r %.2fs' % ( result['status_code'], url, error, result['time']))))
            return result

        try:
            request = tornado.httpclient.HTTPRequest(
                url='%s' % self.phantomjs_proxy, method='POST',
                body=json.dumps(fetch), **request_conf)
            if self.async:
                self.http_client.fetch(request, handle_response)
            else:
                return handle_response(self.http_client.fetch(request))
        except tornado.httpclient.HTTPError as e:
            if e.response:
                return handle_response(e.response)
            else:
                return handle_error(e)
        except Exception as e:
            return handle_error(e)

class PhantomjsMainHandler(tornado.web.RequestHandler):
    def get(self):

        log_str = []
        try:
            host = self.request.host
            path = self.request.uri
            if len(path) < 4 or path[0:4] != 'http':
                if len(host) < 4 or host[0:4] != 'http':
                    url = 'http://' + host + path
                else:
                    url = 'http://' + path
            else:
                url = path

            if url is None:
                url = self.get_argument("url",None)
                if url is None:
                    raise Exception('url is null')
            time0 = datetime.datetime.now()
            log_str.append(__file__ + '|' + str(sys._getframe().f_lineno) + '|' + '|start fetch|' + url + '|' + str(time0))
            fetcher = Fetcher(user_agent='Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.04 (lucid) Firefox/3.6.18")')

            js_run_at = self.get_argument('run','document-end')
            java_script = self.get_argument('java_script', None)

            if not java_script:
                click_id = self.get_argument('click_id', None)
                if click_id:
                    java_script = "function(){ var a = document.getElementById('%s'); var e = document.createEvent('MouseEvents'); e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); return a.dispatchEvent(e);}" % (
                    click_id)

                jump_id = self.get_argument('jump_id', None)
                if jump_id:
                    page_id = self.get_argument('page_id', None)
                    if not page_id:
                        raise Exception('page_id is null')

                    page_value = self.get_argument('page_value', None)
                    if not page_id:
                        raise Exception('page_value is null')

                    page_value = int(page_value)
                    if page_value == 0:
                        raise Exception('page value can not be 0')

                    java_script = "function(){ document.getElementById('%s').value='%d'; var a = document.getElementById('%s'); var e = document.createEvent('MouseEvents'); e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); return a.dispatchEvent(e);}" % (
                    page_id, page_value, jump_id)
                    log_str.append(__file__ + '|' + str(
                        sys._getframe().f_lineno) + '|' + '|java_script|' + js_run_at + '|' + page_id + '|' + str(
                        page_value) + '|' + jump_id)

            res = fetcher.phantomjs_fetch(url=url, js_script=java_script, js_run_at=js_run_at)
            time2 = datetime.datetime.now()
            log_str.append(__file__ + '|' + str(sys._getframe().f_lineno) + '|' + '|finish fetch|' + url + '|' + str(time2))

            if res is not None and isinstance(res,dict):
                content = res.get('content')

                for k,v in res.items():
                    if k != 'content':
                        if k == 'js_script_result':
                            if isinstance(v, unicode):
                                log_str.append('|js_script_result|' + str(v.encode('utf8')))
                            else:
                                log_str.append('|js_script_result|' + str(v))

                cookies = res.get('cookies')
                if cookies:
                    for key,value in cookies.items():
                        value = value.replace('\n','\\n')
                        self.set_cookie(key,value)

                headers = res.get('headers')

                if headers:
                    for key, value in headers.items():
                        if key in ('Date', 'Cache-Control', 'Server', 'Content-Type', 'Location','Last-Modified'):
                            self.add_header(key, value)

                self.set_header('Content-Type', 'text/html; charset=UTF-8')
                status_code = res.get('status_code')
                if str(status_code) == '599':
                    status_code = 502
                    for k, v in res.items():
                        log_str.append(__file__ + '|' + str(sys._getframe().f_lineno) + '|' + '|error|'+ str(k) + ':' + str(v))

                self.set_status(status_code)

                self.write(content)
                if(len(content) < 100):
                    log_str.append(__file__ + '|' + str(sys._getframe().f_lineno) + '|' + '|content<100|'+content)

        except Exception as e:
            str_result = traceback.format_exc()
            log_str.append(__file__ + '|' + str(sys._getframe().f_lineno) + '|' + '|traceback|'+str_result)
            self.set_header("Content-Type", "text/plain")
            self.write(str_result)
        finally:
            logger.log(logging.INFO,__file__ + '|' + str(sys._getframe().f_lineno) + '|---------------------------------------------------------------------------------')

            for i in log_str:
                logger.log(logging.INFO,i)

            logger.log(logging.INFO,__file__ + '|' + str(sys._getframe().f_lineno) + '|---------------------------------------------------------------------------------')

        return

    def post(self):
        log_str = []
        try:
            host = self.request.host
            path = self.request.uri
            if len(path) < 4 or path[0:4] != 'http':
                if len(host) < 4 or host[0:4] != 'http':
                    url = 'http://' + host + path
                else:
                    url = 'http://' + path
            else:
                url = path

            if url is None:
                url = self.get_argument("url", None)
                if url is None:
                    raise Exception('url is null')

            time0 = datetime.datetime.now()
            log_str.append(
                __file__ + '|' + str(sys._getframe().f_lineno) + '|' + '|start fetch|' + url + '|' + str(time0))

            fetcher = Fetcher(user_agent='Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.04 (lucid) Firefox/3.6.18")')
            fetcher.default_options['method'] = 'POST'

            js_run_at = self.get_argument('run', 'document-end')
            java_script = self.get_argument('java_script', None)

            if not java_script:
                click_id = self.get_argument('click_id', None)
                if click_id:
                    java_script = "function(){ var a = document.getElementById('%s'); var e = document.createEvent('MouseEvents'); e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); return a.dispatchEvent(e);}" % (
                        click_id)

                jump_id = self.get_argument('jump_id', None)
                if jump_id:
                    page_id = self.get_argument('page_id', None)
                    if not page_id:
                        raise Exception('page_id is null')

                    page_value = self.get_argument('page_value', None)
                    if not page_id:
                        raise Exception('page_value is null')

                    page_value = int(page_value)
                    if page_value == 0:
                        raise Exception('page value can not be 0')

                    java_script = "function(){ document.getElementById('%s').value='%d'; var a = document.getElementById('%s'); var e = document.createEvent('MouseEvents'); e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); return a.dispatchEvent(e);}" % (
                        page_id, page_value, jump_id)
                    log_str.append(__file__ + '|' + str(
                        sys._getframe().f_lineno) + '|' + '|java_script|' + js_run_at + '|' + page_id + '|' + str(
                        page_value) + '|' + jump_id)

            post_data = self.request.body
            fetcher.default_options['data'] = post_data

            res = fetcher.phantomjs_fetch(url=url, js_script=java_script, js_run_at=js_run_at)
            time2 = datetime.datetime.now()
            log_str.append(
                __file__ + '|' + str(sys._getframe().f_lineno) + '|' + '|finish fetch|' + url + '|' + str(time2))

            if res is not None and isinstance(res, dict):
                content = res.get('content')
                cookies = res.get('cookies')
                if cookies:
                    for key, value in cookies.items():
                        value = value.replace('\n', '\\n')
                        self.set_cookie(key, value)

                headers = res.get('headers')

                if headers:
                    for key, value in headers.items():
                        if key in ('Date', 'Cache-Control', 'Server', 'Content-Type', 'Location', 'Last-Modified'):
                            self.add_header(key, value)

                self.set_header('Content-Type', 'text/html; charset=UTF-8')
                status_code = res.get('status_code')
                if str(status_code) == '599':
                    status_code = 502
                    for k, v in res.items():
                        log_str.append(
                            __file__ + '|' + str(sys._getframe().f_lineno) + '|' + '|error|' + str(k) + ':' + str(v))

                self.set_status(status_code)

                self.write(content)
                if (len(content) < 100):
                    log_str.append(__file__ + '|' + str(sys._getframe().f_lineno) + '|' + '|content<100|' + content)
        except Exception as e:
            str_result = traceback.format_exc()
            log_str.append(__file__ + '|' + str(sys._getframe().f_lineno) + '|' + '|traceback|' + str_result)
            self.set_header("Content-Type", "text/plain")
            self.write(str_result)
        finally:
            logger.log(logging.INFO, __file__ + '|' + str(
                sys._getframe().f_lineno) + '|---------------------------------------------------------------------------------')

            for i in log_str:
                logger.log(logging.INFO, i)

            logger.log(logging.INFO, __file__ + '|' + str(
                sys._getframe().f_lineno) + '|---------------------------------------------------------------------------------')


settings = {
    'debug': False,  # 开发模式
    'gzip': True,  # 支持gzip压缩
    'autoescape': None,
    'xsrf_cookies': False,
    'cookie_secret': 'xxx'
}
application = tornado.web.Application([(r"/",PhantomjsMainHandler),(r"",PhantomjsMainHandler),(r".*",PhantomjsMainHandler),],**settings)

if __name__ == "__main__":
    port = int(sys.argv[1])
    proxy_port = str(port - 10)
    application.listen(port=port)
    tornado.ioloop.IOLoop.instance().start()